﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Cryptography;
using System.Text;

namespace Reversi
{
    /// <summary>
    /// Summary description for DataAccessLayer
    /// </summary>
    public class DataAccessLayer
    {
        private static readonly DataSourceDataContext db = new DataSourceDataContext();


        public DataAccessLayer()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        public static int getWhoseTurn(int game_id)
        {
            int? turn_id = (from a in db.Games
                         where a.id == game_id
                         select a.turn).FirstOrDefault();
            if (turn_id == null)
            {
                turn_id = 0;
            }
            return (int)turn_id;
        }

        public static Game getGame(int game_id)
        {
            return (from a in db.Games
                    where a.id == game_id
                    select a).FirstOrDefault() as global::Game;
        }

        public static global::User getUserFromSession(string session_id)
        {
            return (User)(from u in db.Users
                          where u.sessionid == session_id
                          select u).FirstOrDefault();
        }

        public static string CreateUser(string username, string password)
        {
            MD5 md5hash = MD5.Create();
            DataSourceDataContext db = new DataSourceDataContext();
            string message = "";

            global::User user = (from a in db.Users
                                where a.username == username
                                select a).FirstOrDefault();

            if (user == null)
            {
                string salt = "";
                Random r = new Random();
                for (int i = 0; i < 16; i++)
                {
                    salt += r.Next(0, 9).ToString();
                }

                global::User new_user = new global::User();
                new_user.username = username;
                new_user.passhash = Cryto_Utils.GetMd5Hash(md5hash, password + salt);
                new_user.salt = salt;

                db.Users.InsertOnSubmit(new_user);

                try
                {
                    db.SubmitChanges();
                }
                catch (Exception db_exception)
                {
                    message = db_exception.ToString();
                }

                message = null;
            }
            else
            {
                message = "User exists.";
            }
            return message;
        }

        public static global::User CheckCredentials(string username, string password, bool newSession)
        {
            MD5 md5hash = MD5.Create();
            DataSourceDataContext db = new DataSourceDataContext();


            global::User user = (from a in db.Users
                                 where a.username == username
                                 select a).FirstOrDefault();

            if (user != null)
            {
                if(Cryto_Utils.VerifyMd5Hash(md5hash, password + user.salt, user.passhash))
                {
                    if(newSession)
                    {
                        user.sessionid = Guid.NewGuid().ToString();
                        db.SubmitChanges();
                    }
                }
                else
                {
                    user = null;
                }
            }
            
            return user;
        }

        public static Boolean IsTurn(string SessionID)
        {
            bool is_turn = false;

            int? game_id = GetGameID(SessionID);
            if (game_id != null)
            {
                int turn_id = getWhoseTurn((int) game_id);
                is_turn = getUserFromSession(SessionID).id == turn_id;
            }

            return is_turn;
        }

        public static int? GetGameID(string session_id)
        {
            int? g_id = null;
            int? u_id = (int?)(from u in db.Users
                         where u.sessionid == session_id
                         select u.id).FirstOrDefault();
            if(u_id != null)
            {
                g_id = (int?)(from g in db.Games
                              where g.w_player == u_id || g.b_player == u_id
                              select g.id).FirstOrDefault();

            }
            return g_id;
        }

        public static void BoardSave(string sessionid, Board board)
        {
            int? g_id = GetGameID(sessionid);

            global::Game game = (from t in db.Games
                                 where t.id == g_id
                                 select t).FirstOrDefault();

            if (game == null)
            {
                return;
            }

            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < Board.BOARD_SIZE; i++)
            {
                for (int j = 0; j < Board.BOARD_SIZE; j++)
                {
                    sb.Append(board.squares[i, j].ToString());
                    if (j != Board.BOARD_SIZE - 1)
                    {
                        sb.Append(",");
                    }
                }

                if (i != Board.BOARD_SIZE - 1)
                {
                    sb.Append(";");
                }
            }

            sb.Append(".");

            for (int i = 0; i < Board.BOARD_SIZE; i++)
            {
                for (int j = 0; j < Board.BOARD_SIZE; j++)
                {
                    sb.Append(board.safeDiscs[i, j].ToString());
                    if (j != Board.BOARD_SIZE - 1)
                    {
                        sb.Append(",");
                    }
                }

                if (i != Board.BOARD_SIZE - 1)
                {
                    sb.Append(";");
                }
            }

            sb.Append(".");
            sb.Append(board.blackCount);
            sb.Append(".");
            sb.Append(board.whiteCount);
            sb.Append(".");
            sb.Append(board.emptyCount);
            sb.Append(".");
            sb.Append(board.blackSafeCount);
            sb.Append(".");
            sb.Append(board.whiteSafeCount);

            game.gamestate = sb.ToString();



            if (game.turn == game.w_player)
            {
                game.turn = game.b_player;
            }
            else
            {
                game.turn = game.w_player;
            }

            //try
            //{
            //    db.SubmitChanges();
            //}
            //catch (Exception e)
            //{
            //    Console.WriteLine(e);
            //}
            db.SubmitChanges();
        }

        public static Board BoardLoad(string SessionID)
        {
            Board temp = new Board();
            temp.SetForNewGame();

            int[,] squares = new int[Board.BOARD_SIZE, Board.BOARD_SIZE];
            bool[,] safe = new bool[Board.BOARD_SIZE, Board.BOARD_SIZE];

            int? ID = getUserFromSession(SessionID).id;

            Console.WriteLine("User Id is: " + ID);

            if (ID == null)
                return null;

            global::Game game = (from t in db.Games
                         where t.w_player == ID || t.b_player == ID
                         select t).FirstOrDefault();

            // If not in any game -> join an empty slow, no empty slot -> create game.
            if (game == null)
            {
                game = (from t in db.Games
                        where t.b_player == null
                        select t).FirstOrDefault();

                if (game == null)
                {
                    //create a new game and set player as white.
                    game = new global::Game();
                    game.turn = null;
                    game.w_player = ID.Value;

                    db.Games.InsertOnSubmit(game);

                    try
                    {
                        db.SubmitChanges();
                        game = (from t in db.Games
                         where t.w_player == ID || t.b_player == ID
                         select t).FirstOrDefault();

                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                    }
                }
                else
                {
                    //join the player to the existing game as black.
                    game.b_player = ID.Value;
                    if (game.turn == null)
                    {
                        game.turn = ID.Value;
                    }
                    try
                    {
                        db.SubmitChanges();
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                    }
                }
            }

            //at this point game is a valid object

            string gs = game.gamestate;
            if (gs == null)
            {
                //gave never saved, save a new gamestate into gamestate.
                BoardSave(SessionID, temp);
            }
            else
            {
                //game in progress, load from DB.
                //split into the various values
                string[] vals = gs.Split(".".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

                //split the arrays into lines
                string[] squares_lines = vals[0].Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                string[] safe_lines = vals[1].Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                
                for (int i = 0; i < Board.BOARD_SIZE; i++)
                {
                    //split each line into ints/bools to be parsed.
                    string[] line_tokens = squares_lines[i].Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    string[] safe_tokens = safe_lines[i].Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    
                    //set the values in the temp Board
                    for (int j = 0; j < Board.BOARD_SIZE; j++)
                    {
                        squares[i, j] = int.Parse(line_tokens[j]);
                        safe[i, j] = bool.Parse(safe_tokens[j]);
                    }
                }

                //Pull the rest of the saved data.
                int b_count = int.Parse(vals[2]);
                int w_count = int.Parse(vals[3]);
                int e_count = int.Parse(vals[4]);
                int bs_count = int.Parse(vals[5]);
                int ws_count = int.Parse(vals[6]);

                //Set everything in temp to what it needs to be.
                temp.squares = squares;
                temp.safeDiscs = safe;
                temp.blackCount = b_count;
                temp.whiteCount = w_count;
                temp.emptyCount = e_count;
                temp.blackSafeCount = bs_count;
                temp.whiteSafeCount = ws_count;
            }

            return temp;
        }

        public static Boolean Refresh(string sessionID)
        {
            //bool? refresh = (from t in db.Sessions
            //                  where t.sessionid == sessionID
            //                  select t.refresh).FirstOrDefault();

            //if (refresh == true)
            //    return true;

            return false;
        }
    }
}